#include <unistd.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <cstring>
#include "ioscheduler.h"

static bool debug = false;

namespace lzp {

IOManager* IOManager::GetThis() {
    // todo 这里没弄懂，为什么可以返回基类指针
    return dynamic_cast<IOManager*>(Scheduler::GetThis);
}

/**
 * @brief 获取EventContext
 * @param[in] event 事件类型
 * @return 返回对应事件的上下文EventContext
 */
IOManager::FdContext::EventContext& IOManager::FdContext::getEventContext(Event event) {
    assert(event == READ || event == WRITE);
    switch (event)
    {
    case READ:
        return read;
    case WRITE:
        return write;
    }
    throw std::invalid_argument("Unsupported event type");
}

/**
 * @brief 重置事件上下⽂
 * @param[in, out] ctx 待重置的事件上下⽂对象
 */
void IOManager::FdContext::resetEventContext(EventContext &ctx) {
    ctx.scheduler = nullptr;
    ctx.fiber.reset();
    ctx.cb = nullptr;
}

/**
 * @brief 触发事件
 * @details 根据事件类型调⽤对应上下⽂结构中的调度器去调度回调协程或回调函数
 * @param[in] event 事件类型
 */
void IOManager::FdContext::triggerEvent(Event event) { 
    assert(events & event);

    // delete event，将event从events中去除
    events = (Event)(events & ~event);

    // 获取EventContext，里面保存了对应事件的回调函数
    EventContext& ctx = getEventContext(event);
    if (ctx.cb) {
        // 将回调任务加入到调度器任务队列中，并且tickle通知调度器
        ctx.scheduler->schedule(&ctx.cb);
    }
    else {
        ctx.scheduler->schedule(&ctx.fiber);
    }

    // reset event context
    resetEventContext(ctx);
    return;
}

/**
 * @brief 构造函数
 * @param[in] threads 线程数量
 * @param[in] use_caller 是否将调⽤线程包含进去
 * @param[in] name 调度器的名称
 */
IOManager::IOManager(size_t threads, bool use_caller, const std::string& name):
Scheduler(threads, use_caller, name), TimerManager() {
    // 创建epoll描述符
    m_epfd = epoll_create(5000);
    assert(m_epfd > 0);

    // 创建pipe，m_tickleFds[0]读端，m_tickleFds[1]写端
    int rt = pipe(m_tickleFds);
    assert(!rt);

    // 注册pipe读句柄的可读事件，用于tickle调度协程，通过epoll_event.data.fd保存描述符
    epoll_event event;
    event.events = EPOLLIN | EPOLLET; // 监测读事件的发生，Edge Triggered，ET边沿触发，内核不会反复通知事件就绪
    event.data.fd = m_tickleFds[0];

    // 设置为非阻塞方式，配合边缘触发
    rt = fcntl(m_tickleFds[0], F_SETFL, O_NONBLOCK);
    assert(!rt);

    // 将管道的读描述符m_tickleFds[0]加入epoll多路复用，如果管道可读，idle中的epoll_wait会返回
    rt = epoll_ctl(m_epfd, EPOLL_CTL_ADD, m_tickleFds[0], &event);
    assert(!rt);

    contextResize(32);

    // 启动调度器
    Scheduler::start();
}

IOManager::~IOManager() {
    stop();
    close(m_epfd);
    close(m_tickleFds[0]);
    close(m_tickleFds[1]);

    for (size_t i = 0; i < m_fdContexts.size(); ++i) {
        if (m_fdContexts[i]) {
            delete m_fdContexts[i];
        }
    }
}

// no lock
void IOManager::contextResize(size_t size) {
    m_fdContexts.resize(size);
    for (size_t i = 0; i < m_fdContexts.size(); ++i) {
        if (m_fdContexts[i] == nullptr) {
            m_fdContexts[i] = new FdContext();
            m_fdContexts[i]->fd = i;
        }
    }
}

/**
 * @brief 添加事件，事件被触发后就会被删除，不会被重复触发
 * @details fd描述符发⽣了event事件时执⾏cb函数
 * @param[in] fd socket句柄
 * @param[in] event 事件类型
 * @param[in] cb 事件回调函数，如果为空，则默认把当前协程作为回调执⾏体
 * @return 添加成功返回0,失败返回-1
 */
int IOManager::addEvent(int fd, Event event, std::function<void()> cb) {
    FdContext* fd_ctx = nullptr;
    std::shared_lock<std::shared_mutex> read_lock(m_mutex);

    // 找到fd对应的FdContext，如果不存在，那就分配一个
    if ((int)m_fdContexts.size() > fd) {
        fd_ctx = m_fdContexts[fd];
        read_lock.unlock();
    }
    else {
        read_lock.unlock();
        std::unique_lock<std::shared_mutex> write_lock(m_mutex);
        contextResize(fd * 1.5);
        fd_ctx = m_fdContexts[fd];
    }

    std::lock_guard<std::mutex> lock(fd_ctx->mutex);

    // 同一个fd不允许添加相同的事件
    if (fd_ctx->events & event)
        return -1;
    
    // 添加新事件到epoll_events中，如果已经有的话则修改
    int op = fd_ctx->events ? EPOLL_CTL_MOD : EPOLL_CTL_ADD;
    epoll_event epevent;
    // READ == EPOLLIN，WRITE == EPOLLOUT
    epevent.events   = EPOLLET | fd_ctx->events | event;
    // 通过epoll_event的私有数据指针data.ptr来保存FdContext结构体
    epevent.data.ptr = fd_ctx;

    int rt = epoll_ctl(m_epfd, op, fd, &epevent);
    if (rt) {
        std::cerr << "addEvent::epoll_ctl failed: " << strerror(errno) << std::endl; 
        return -1;
    }

    // 待执行IO事件数加1
    ++m_pendingEventCount;

    // 更新fdcontext中的注册事件
    fd_ctx->events = (Event)(fd_ctx->events | event);

    // 找到这个fd的event事件对应的EventContext，对其中的scheduler, cb, fiber进⾏赋值
    FdContext::EventContext& event_ctx = fd_ctx->getEventContext(event);
    assert(!event_ctx.scheduler && !event_ctx.fiber && !event_ctx.cb);

    // 赋值scheduler和回调函数，如果回调函数为空，则把当前协程当成回调执⾏体
    event_ctx.scheduler = Scheduler::GetThis();
    if (cb) {
        event_ctx.cb.swap(cb);
    } 
    else {
        event_ctx.fiber = Fiber::GetThis();
        assert(event_ctx.fiber->getState() == Fiber::RUNNING);
    }
    return 0;
}

/**
 * @brief 删除事件
 * @param[in] fd socket句柄
 * @param[in] event 事件类型
 * @attention 不会触发事件
 * @return 是否删除成功
 */
bool IOManager::delEvent(int fd, Event event) {
    FdContext *fd_ctx = nullptr;
    std::shared_lock<std::shared_mutex> read_lock(m_mutex);

    // 找到fd对应的FdContext
    if ((int)m_fdContexts.size() > fd) {
        fd_ctx = m_fdContexts[fd];
        read_lock.unlock();
    }
    else {
        read_lock.unlock();
        return false;
    }

    std::lock_guard<std::mutex> lock(fd_ctx->mutex);
    // 事件不存在则直接返回
    if (!(fd_ctx->events & event)) {
        return false;
    }

    // 清除指定的事件，表示不关⼼这个事件了，如果清除之后结果为0，则从epoll_wait中删除该⽂件描述符
    Event new_events = (Event)(fd_ctx->events & ~event);
    int op           = new_events ? EPOLL_CTL_MOD : EPOLL_CTL_DEL;
    epoll_event epevent;
    epevent.events   = EPOLLET | new_events;
    epevent.data.ptr = fd_ctx;

    int rt = epoll_ctl(m_epfd, op, fd, &epevent);
    if (rt) {
        std::cerr << "delEvent::epoll_ctl failed: " << strerror(errno) << std::endl; 
        return false;
    }

    // 待执⾏事件数减1
    --m_pendingEventCount;

    // 更新fdcontext
    fd_ctx->events = new_events;

    // 重置该fd对应的event事件上下⽂
    FdContext::EventContext& event_ctx = fd_ctx->getEventContext(event);
    fd_ctx->resetEventContext(event_ctx);
    return true;
}

/**
 * @brief 取消事件
 * @param[in] fd socket句柄
 * @param[in] event 事件类型
 * @attention 如果该事件被注册过回调，那就触发⼀次回调事件
 * @return 是否删除成功
 */
bool IOManager::cancelEvent(int fd, Event event) {
    FdContext *fd_ctx = nullptr;
    
    std::shared_lock<std::shared_mutex> read_lock(m_mutex);
    if ((int)m_fdContexts.size() > fd) {
        fd_ctx = m_fdContexts[fd];
        read_lock.unlock();
    }
    else {
        read_lock.unlock();
        return false;
    }

    std::lock_guard<std::mutex> lock(fd_ctx->mutex);

    // 事件不存在则直接返回
    if (!(fd_ctx->events & event)) {
        return false;
    }

    // 清除指定的事件，表示不关⼼这个事件了，如果清除之后结果为0，则从epoll_wait中删除该⽂件描述符
    Event new_events = (Event)(fd_ctx->events & ~event);
    int op           = new_events ? EPOLL_CTL_MOD : EPOLL_CTL_DEL;
    epoll_event epevent;
    epevent.events   = EPOLLET | new_events;
    epevent.data.ptr = fd_ctx;

    int rt = epoll_ctl(m_epfd, op, fd, &epevent);
    if (rt) {
        std::cerr << "cancelEvent::epoll_ctl failed: " << strerror(errno) << std::endl; 
        return false;
    }

    --m_pendingEventCount;

    // 删除前触发一次事件
    fd_ctx->triggerEvent(event);    
    return true;
}

/**
 * @brief 取消fd的所有事件
 * @details 所有被注册的回调事件在cancel之前都会被执⾏⼀次
 * @param[in] fd socket句柄
 * @return 是否删除成功
 */
bool IOManager::cancelAll(int fd) {
    FdContext *fd_ctx = nullptr;
    
    std::shared_lock<std::shared_mutex> read_lock(m_mutex);
    if ((int)m_fdContexts.size() > fd) {
        fd_ctx = m_fdContexts[fd];
        read_lock.unlock();
    }
    else {
        read_lock.unlock();
        return false;
    }

    std::lock_guard<std::mutex> lock(fd_ctx->mutex);
    
    // fd上没有事件则直接返回
    if (!fd_ctx->events) {
        return false;
    }

    // 删除全部事件
    int op = EPOLL_CTL_DEL;
    epoll_event epevent;
    epevent.events   = 0;
    epevent.data.ptr = fd_ctx;

    int rt = epoll_ctl(m_epfd, op, fd, &epevent);
    if (rt) {
        std::cerr << "IOManager::epoll_ctl failed: " << strerror(errno) << std::endl; 
        return false;
    }

    // 删除前触发一次已注册的事件
    if (fd_ctx->events & READ) {
        fd_ctx->triggerEvent(READ);
        --m_pendingEventCount;
    }
    if (fd_ctx->events & WRITE) {
        fd_ctx->triggerEvent(WRITE);
        --m_pendingEventCount;
    }

    assert(fd_ctx->events == 0);
    return true;
}

/**
 * @brief 通知调度器有任务要调度
 * @details 写pipe让idle协程从epoll_wait退出，待idle协程yield之后Scheduler::run就可以调度其他任务
 * 如果当前没有空闲调度线程，那就没必要发通知
 */
void IOManager::tickle() 
{
    // no idle threads
    if(!hasIdleThreads()) {
        return;
    }
    int rt = write(m_tickleFds[1], "T", 1);
    assert(rt == 1);
}

bool IOManager::stopping() 
{
    uint64_t timeout = getNextTimer();
    // 对于IOManager⽽⾔，必须等所有待调度的IO事件都执⾏完并且没有剩余定时器才可以退出
    return timeout == ~0ull && m_pendingEventCount == 0 && Scheduler::stopping();
}

/**
 * @brief idle协程
 * @details 对于IO协程调度来说，应阻塞在等待IO事件上，idle退出的时机是epoll_wait返回，对应的操作是tickle或注册的IO事件就绪
 * 调度器无调度任务时会阻塞idle协程上，对IO调度器⽽⾔，idle状态应该关注两件事，⼀是有没有新的调度任务，对应Schduler::schedule()，
 * 如果有新的调度任务，那应该立即退出idle状态，并执行对应的任务；二是关注当前注册的所有IO事件有没有触发，如果有触发，那么应该执⾏
 * IO事件对应的回调函数
 */
void IOManager::idle() 
{    
    // 设置一次epoll_wait最多监测256个就绪事件，如果就绪事件超过了256，则会在下轮epoll_wait继续处理
    static const uint64_t MAX_EVNETS = 256;
    epoll_event *events = new epoll_event[MAX_EVNETS];

    while (true) 
    {
        if(debug) std::cout << "IOManager::idle(),run in thread: " << Thread::GetThreadId() << std::endl; 

        if(stopping()) {
            if(debug) std::cout << "name = " << getName() << " idle exits in thread: " << Thread::GetThreadId() << std::endl;
            break;
        }

        // 阻塞在epoll_wait上，等待事件发生
        int rt = 0;
        while(true) {
            static const uint64_t MAX_TIMEOUT = 5000;
            // 这里将epoll_wait的超时事件改用为当前定时器最小的超时时间代替
            // epoll_wait返回后，根据当前的绝对时间把已超时的所有定时器收集起来，执行它们的回调函数
            uint64_t next_timeout = getNextTimer();
            next_timeout = std::min(next_timeout, MAX_TIMEOUT);

            // idle协程阻塞在epoll_wait上，当有事件发生时被唤醒，rt为发生事件的fd个数
            rt = epoll_wait(m_epfd, events, MAX_EVNETS, (int)next_timeout);
            // EINTR -> retry，当捕获到信号导致epoll_wait返回EINTR的时候，重新epoll_wait
            if(rt < 0 && errno == EINTR) {
                continue;
            }
            // 如果rt >= 0表示有事件发生，退出循环
            else {
                break;
            }
        };

        // 取出所有超时定时器的回调函数，并且将其加入到调度器的任务队列中，等待被调度执行
        std::vector<std::function<void()>> cbs;
        listExpiredCb(cbs);
        if(!cbs.empty()) {
            for(const auto &cb : cbs) {
                schedule(cb);
            }
            cbs.clear();
        }
        
        // 遍历epoll_wait返回的所有发生的事件，根据epoll_event的私有指针找到对应的FdContext，进⾏事件处理
        for (int i = 0; i < rt; ++i) {
            epoll_event& event = events[i];

            // ticklefd[0]⽤于通知协程调度，这时只需要把管道⾥的内容读完即可，本轮idle结束Scheduler::run会重新执⾏协程调度
            // tickle往m_tickleFds[1]里写数据的目的就是为了唤醒阻塞在epoll_wait上的idle，然后执行yield()去执行其他任务
            if (event.data.fd == m_tickleFds[0]) {
                uint8_t dummy[256];
                // edge triggered -> exhaust
                while (read(m_tickleFds[0], dummy, sizeof(dummy)) > 0);
                continue;
            }

            // 其他事件，则取出保存在epoll_event.data.ptr中的FdContext
            FdContext *fd_ctx = (FdContext *)event.data.ptr;
            std::lock_guard<std::mutex> lock(fd_ctx->mutex);

            /**
             * EPOLLERR: 出错，读如写读端已经关闭的pipe
             * EPOLLHUP: 套接字对端关闭
             * 出现这两种事件，应该同时触发fd的读和写事件，否则有可能出现注册的事件永远执⾏不到的情况
             */
            if (event.events & (EPOLLERR | EPOLLHUP)) {
                event.events |= (EPOLLIN | EPOLLOUT) & fd_ctx->events;
            }

            // 判断读和写事件的回调函数是否被注册
            int real_events = NONE;
            if (event.events & EPOLLIN) {
                real_events |= READ;
            }
            if (event.events & EPOLLOUT) {
                real_events |= WRITE;
            }
            if ((fd_ctx->events & real_events) == NONE) {
                continue;
            }

            // 删除已经发生的事件，将剩下的事件重新加入epoll_wait
            // 如果剩下的事件为0，表示这个fd已经不需要关注了，直接从epoll中删除
            int left_events = (fd_ctx->events & ~real_events); // real_events是已发生的事件，left_event是没有发生的事件
            int op = left_events ? EPOLL_CTL_MOD : EPOLL_CTL_DEL;
            event.events    = EPOLLET | left_events;

            // 将剩下的事件重新加入epoll_wait
            int rt2 = epoll_ctl(m_epfd, op, fd_ctx->fd, &event);
            if (rt2) {
                std::cerr << "idle::epoll_ctl failed: " << strerror(errno) << std::endl; 
                continue;
            }

            // 处理已发生的事件，也就是让调度器调度FdContext注册的回调函数或协程
            if (real_events & READ) {
                fd_ctx->triggerEvent(READ);
                --m_pendingEventCount;
            }
            if (real_events & WRITE) {
                fd_ctx->triggerEvent(WRITE);
                --m_pendingEventCount;
            }
        } 

        // 处理完所有事件后，idle协程yield，返回协程调度函数Scheduler::run()，重新选择新的任务执行
        Fiber::GetThis()->yield();
  
    }
}

void IOManager::onTimerInsertedAtFront() 
{
    tickle();
}

}